<?php

class Exts_Dependency_Container {
	/*
	 * El container esta pensado para ser singleton. Un solo repositorio de objetos.
	 * */
	protected static $_instance;
	
	/*
	 * Al agregar configuraciones del repositorio, hay que indicar la forma de instanciacion. 
	 * Para pasar los parametros tipo 'Instantiator' se pueden utilizar las variables de clase estaticas
	 
	 * Exts_Dependency_Container::$SingletonStrategy -- Solo instancia una vez la clase solicitada, luego devuelve la misma refernecia
	 * Exts_Dependency_Container::$NormalStrategy -- Devuelve una instancia por llamado
	 * Exts_Dependency_Container::$InyectionStrategy -- Devuelve una instancia de una 3ra clase especificada en la configuracion.
	 * 
	 * Que identifican los distintos tipos de instanciaciones hasta ahora escritos. 
	 * 
	 * */
	public static $SingletonStrategy;
	public static $NormalStrategy;
	public static $InyectionStrategy;

	
	/*
	 * Mapa de clases > instanciadores
	 **/
	protected $_classInstantiators  = array();
	/*
	 *	Array de clases denegadas. Esto esta bueno para cuando se quiere probar la falta de una clase (por ejemplo para borrarla) 
	 **/
	protected $_denyInstancesOf = array ();

	private function __construct(){
		
	}
	/*
	 * Devuelve una instancia de limpia del container. Esta pensado para que la llame el objeto loader, de configuracion. 
	 * */	
	public static function UnconfiguredContainerInstance () {

		if (empty(self::$SingletonStrategy)) {
			self::$SingletonStrategy = Exts_Dependency_Instantiator_Singleton::InstantiatorInstance();
		}
		if (empty(self::$NormalStrategy)) {
			self::$NormalStrategy = Exts_Dependency_Instantiator_Normal::InstantiatorInstance();
		}
		if (empty(self::$InyectionStrategy)) {
			self::$InyectionStrategy = Exts_Dependency_Instantiator_Inyector::InstantiatorInstance();
		}
		
		return new Exts_Dependency_Container();;
	}
	
	/* 
	 * Esta es la forma en que uno pide un container, en general tambien debiera ser innecesario la llamada a este metodo,
	 * pero si se necesita el container ya cargado y seteado como instanciado (singleton) este es el metodo que hay que llamar 
	 **/
	public static function ContainerInstance () {
		if (empty (self::$_instance)) {
			$loader = new Exts_Dependency_Configuration_Loader();
			$loader->loadContainer();
			self::$_instance = $loader->getContainer();
		}
		return self::$_instance;
	}
	/*
	 * Metodo estatico que devuelve una instancia de una clase dada. 
	 * Esta debiera ser el punto de entrada mas usado al container.
	 * 
	 * En caso de que la clase que se pide no este configurada, se supone la instanciación normal. 
	 * Para evitar que una clase sea instanciada por este medio debe configurarse como 'Deny'
	 * 
	 **/
	public static function instance ($className){
		
		return Exts_Dependency_Container::ContainerInstance()->_instance($className);
	}
	/*
	 * En caso de que se use el objeto container a parte, las instancias de clases se les piden por este mensaje.
	 * */
	public function _instance ($className) {
		
		if (in_array($className, $this->_denyInstancesOf)) {
			throw new Exts_Exception (" Se intentó instanciar $className, pero esta prohibida para ser instanciada por el container ");
		}
		if (array_key_exists($className, $this->_classInstantiators)){
			return $this->_classInstantiators[$className]->instance($className);
		}
		return Exts_Dependency_Container::$NormalStrategy->instance($className);
	}

	/*
	 * Metodo para denegar instanciaciones
	 */
	public function denyInstances ($className){
		$this->_denyInstancesOf[] = $className;
	}

	/*
	 * Metodo para configurar instanciadores 
	 **/
	public function addInstantiatorFor ($className, $instantiator){
		$this->_classInstantiators[$className] = $instantiator;
		return $this;
	}

}

